/*----------------------
  PlayMidiLib.c

  Routines to play midi notes in Windows with retuning to any SCALA scale

  http://www.tunesmithy.co.uk/playmidilib/index.htm

  E-mail support@tunesmithy.co.uk

  (c) Robert Walker 2002
----------------------*/

#if defined(_WIN32)
    #pragma warning(disable:4996)
    #pragma warning(disable:4133)
    #pragma warning(disable:4244)
#endif

#include <windows.h>
#include <stdio.h>
#include <errno.h>
#include <process.h>
#include <math.h>
#include "PlayMidiLib.h"
#include <mmsystem.h>
#define PM_MIDI_NOTE_ON 0x90
#define PM_MIDI_NOTE_OFF 0x80
#define PM_MIDI_SET_PATCH 0xC0
#define PM_MIDI_CONTROLLER 0xB0
#define PM_MIDI_PITCH_BEND 0xE0
#define PM_RPN_COARSE_COARSE 0x65
#define PM_RPN_COARSE_FINE 0x64
#define PM_DATA_ENTRY_PB_COARSE 0X06
#define PM_DATA_ENTRY_PB_FINE 0X26
 //so want to solve for n in 2^(n/12)*440==dFreq
 //i.e. (n/12)*log(2)=log(dFreq/440)
#define PM_IPITCH_BEND_BY_SEMITONE 4096
#define PM_IPITCH_BEND_BY_WHOLE_TONE 8192
#define PM_IPITCH_BEND_ZERO_POS PM_IPITCH_BEND_BY_WHOLE_TONE
#define PM_MAX_MIDI_VELOCITY 127
#define PM_MAX_MIDI_NOTES 128
#define PM_TOP_PM_MIDI_NOTE 127
#define PM_MAX_MIDI_VOICES 128
#define PM_NON_MELODIC_PERCUSSION_CHANNEL 9
#define PM_MAX_MIDI_PITCH_BEND ((1<<14)-1)

#define pm_safe_exp(d) exp(min(308,max(-308,d)))
#define pm_safe_log(d) log(max(1e-50,d))
#define PM_NEAREST_INT(dv) (int)floor((dv)+0.5)
#define pm_find_cents_for(x) (1200*pm_safe_log(x)/log(2))
#define pm_find_value_for_cents(x) pm_safe_exp(((x)/1200)*log(2))
#define PM_SEMITONES_FOR_PITCH_BEND(dPitchBend) (((dPitchBend)-PM_IPITCH_BEND_ZERO_POS)/(double)PM_IPITCH_BEND_BY_SEMITONE)
#define PM_PITCH_BEND_FOR_SEMITONES(dSemitones) (PM_IPITCH_BEND_ZERO_POS+(int)((dSemitones)*PM_IPITCH_BEND_BY_SEMITONE))
#define PlayMidi_MIDINoteForFrequency(dFreq) (69+(12*pm_safe_log((dFreq)/440)/log(2.0)))
#define PlayMidi_FrequencyForMIDINote(dMIDINote) (440.0*pm_safe_exp(log(2)*((dMIDINote)-69.0)/12.0))

#ifdef _DEBUG
#define PM_MAX_NOTES_IN_PLAY 128
#define PM_MAX_NOTES_IN_SCALE 128
#else
#define PM_MAX_NOTES_IN_PLAY (16*128)
#define PM_MAX_NOTES_IN_SCALE 1024
#endif
#define PM_MAX_MIDI_CHANNELS 16
#define PM_MAX_MIDI_CHANNELS_ARRAYS MAX_QT_MIDI_CHANNELS // for Quicktime
#define PM_MAX_MIDI_CHANNELS_AVAILABLE (PlayMidi_bUseQT?MAX_QT_MIDI_CHANNELS:16)
#define MAX_SZ_MOST_RECENT_SCALE 1024
char szMostRecentScale[MAX_SZ_MOST_RECENT_SCALE];

#include "QTMusicLib.h"
static BOOL PlayMidi_first_scale_made;
static int PlayMidi_iScaleDegreeInPlay[PM_MAX_NOTES_IN_PLAY];
static int PlayMidi_iChannelInPlay[PM_MAX_NOTES_IN_PLAY];
static int PlayMidi_iNoteInPlay[PM_MAX_NOTES_IN_PLAY];
static double PlayMidi_d1o1MultiplierInPlay[PM_MAX_NOTES_IN_PLAY];
static int PlayMidi_inotes_in_play;
static HMIDIOUT PlayMidi_hMidiOut;
static double PlayMidi_dpitch_bend_for_channel[PM_MAX_MIDI_CHANNELS_ARRAYS];
static int PlayMidi_nnotes_in_play_for_channel[PM_MAX_MIDI_CHANNELS_ARRAYS];
static DWORD PlayMidi_dwtime_for_last_note_ends[PM_MAX_MIDI_CHANNELS_ARRAYS];
static double PlayMidi_dScale[PM_MAX_NOTES_IN_SCALE];
static int PlayMidi_iNotesInScale;
static double PlayMidi_d1o1Frequency,dMidiOutFirstNote=60.0;
static int PlayMidi_iVoice;
static DWORD PlayMidi_dwtopen;
static BOOL PlayMidi_bOpened;
static BOOL PlayMidi_bUseQT;
static HINSTANCE hQTJavaNative,hQTMusic;
static PROC pQTMusic_CleanUp_SME,pQTMusic_Initialise_SME
,pQTMusic_Delay,pQTMusic_SendMidiEvent;
static int PlayMidi_iMidiOutDevSel;
char *PlayMidi_szMidiVoices[]=
{
 "Acoustic Grand Piano",//        0,
 "Bright Acoustic Piano",//       1,
 "Electric Grand Piano",//        2,
 "Honky-tonk Piano",//            3,
 "Rhodes Piano",//                4,
 "Chorused Piano",//              5,
 "Harpsichord",//                 6,
 "Clavinet",//                    7,
 "Celesta",//                     8,
 "Glockenspiel",//                9,
 "Music Box",//                   10,
 "Vibraphone",//                  11,
 "Marimba",//                     12,
 "Xylophone",//                   13,
 "Tubular Bells",//               14,
 "Dulcimer",//                    15,
 "Hammond Organ",//               16,
 "Percussive Organ",//            17,
 "Rock Organ",//                  18,
 "Church Organ",//                19,
 "Reed Organ",//                  20,
 "Accordian",//                   21,
 "Harmonica",//                   22,
 "Tango Accordian",//             23,
 "Acoustic Guitar (nylon)",//     24,
 "Acoustic Guitar (steel)",//     25,
 "Electric Guitar (jazz)",//      26,
 "Electric Guitar (clean)",//     27,
 "Electric Guitar (muted)",//     28,
 "Overdriven Guitar",//           29,
 "Distortion Guitar",//           30,
 "Guitar Harmonics",//            31,
 "Acoustic Bass",//               32,
 "Electric Bass (finger)",//      33,
 "Electric Bass (pick)",//        34,
 "Fretless Bass",//               35,
 "Slap Bass 1",//                 36,
 "Slap Bass 2",//                 37,
 "Synth Bass 1",//                38,
 "Synth Bass 2",//                39,
 "Violin",//                      40,
 "Viola",//                       41,
 "Cello",//                       42,
 "Contrabass",//                  43,
 "Tremolo Strings",//             44,
 "Pizzicato Strings",//           45,
 "Orchestral Harp",//             46,
 "Timpani",//                     47,
 "String Ensemble 1",//           48,
 "String Ensemble 2",//           49,
 "Synth Strings 1",//             50,
 "Synth Strings 2",//             51,
 "Choir Aahs",//                  52,
 "Voice Oohs",//                  53,
 "Synth Voice",//                 54,
 "Orchestra Hit",//               55,
 "Trumpet",//                     56,
 "Trombone",//                    57,
 "Tuba",//                        58,
 "Muted Trumpet",//               59,
 "French Horn",//                 60,
 "Brass Section",//               61,
 "Synth Brass 1",//               62,
 "Synth Brass 2",//               63,
 "Soprano Sax",//                 64,
 "Alto Sax",//                    65,
 "Tenor Sax",//                   66,
 "Baritone Sax",//                67,
 "Oboe",//                        68,
 "Cor Anglais (English Horn)",//  69,
 "Bassoon",//                     70,
 "Clarinet",//                    71,
 "Piccolo",//                     72,
 "Flute",//                       73,
 "Recorder",//                    74,
 "Pan Flute",//                   75,
 "Bottle Blow",//                 76,
 "Shakuhachi",//                  77,
 "Whistle",//                     78,
 "Ocarina",//                     79,
 "Lead 1 (square)",//             80,
 "Lead 2 (sawtooth)",//           81,
 "Lead 3 (caliope lead)",//       82,
 "Lead 4 (chiff lead)",//         83,
 "Lead 5 (charang)",//            84,
 "Lead 6 (voice)",//              85,
 "Lead 7 (fifths)",//             86,
 "Lead 8 (brass + lead)",//       87,
 "Pad 1 (new age)",//             88,
 "Pad 2 (warm)",//                89,
 "Pad 3 (polysynth)",//           90,
 "Pad 4 (choir)",//               91,
 "Pad 5 (bowed)",//               92,
 "Pad 6 (metallic)",//            93,
 "Pad 7 (halo)",//                94,
 "Pad 8 (sweep)",//               95,
 "FX 1 (rain)",//                 96,
 "FX 2 (soundtrack)",//           97,
 "FX 3 (crystal)",//              98,
 "FX 4 (atmosphere)",//           99,
 "FX 5 (brightness)",//           100,
 "FX 6 (goblins)",//              101,
 "FX 7 (echoes)",//               102,
 "FX 8 (sci-fi)",//               103,
 "Sitar",//                       104,
 "Banjo",//                       105,
 "Shamisen",//                    106,
 "Koto",//                        107,
 "Kalimba",//                     108,
 "Bagpipe",//                     109,
 "Fiddle",//                      110,
 "Shanai",//                      111,
 "Tinkle Bell",//                 112,
 "Agogo",//                       113,
 "Steel Drums",//                 114,
 "Woodblock",//                   115,
 "Taiko Drum",//                  116,
 "Melodic Tom",//                 117,
 "Synth Drum",//                  118,
 "Reverse Cymbal",//              119,
 "Guitar Fret Noise",//           120,
 "Breath Noise",//                121,
 "Seashore",//                    122,
 "Bird Tweet",//                  123,
 "Telephone Ring",//              124,
 "Helicopter",//                  125,
 "Applause",//                    126,
 "Gunshot"//                      127
};

static int PlayMidi_FillListOrComboWithMidiOutDevices(HWND hwnd,BOOL is_combo);
static int PlayMidi_FillListOrComboWithMidiVoices(HWND hwnd,BOOL is_combo);
static void PlayMidi_DoBendForScaleDegreeExtraParam(int flags,int ipos_in_scale);
static void PlayMidi_AddNoteInPlayRecord(int ipos_in_scale,double dMultiplier,int iChannelAP,int iNoteToPlay);
static void PlayMidi_RemoveNoteInPlayRecord(int ipos_in_list);
static BOOL PlayMidi_bFindNoteRecord(int iScaleDegree,int iChannel,int iNote,int *pi);
#define OK_TO_SWITCH_OFF 1
#define NO_REBEND_FOR_MOST_RECENT_CHANN 2 // for PlayMidi_FindChannelNoteAndPitchBend(..)
static void PlayMidi_FindChannelNoteAndPitchBend(int flags,int *piChannelAP,int *piNoteToPlay,double *pdPitchBend,double dFreq);
static DWORD PlayMidi_MidiPitchBend(HMIDIOUT hMidi,int iChannel,double dPitchBend);
static DWORD PlayMidi_MidiOutShortMessage(HMIDIOUT hMidi,int iStatus,int iChannel
                    ,int iData1,int iData2,int size
                    );
static DWORD PlayMidi_MidiNoteOff(HMIDIOUT hMidi,int iChannel,int iNote,int iVel);
static DWORD PlayMidi_MidiNoteOn(HMIDIOUT hMidi,int iChannel,int iNote,int iVel);
static void PlayMidi_MidiSetPitchBendRange(HMIDIOUT hMidi,int iChannel,int coarse_range,int fine_range);
static void PlayMidi_SetPitchBendRanges(void);
static void PlayMidi_ResetMidiOutChannelStateRecords(void);
static int PlayMidi_substrcmpi(const char * sz,const char * szsubstr);
static int PlayMidi_remove_duplicated_white_space(char *sz);
static double PlayMidi_dFindMultiplierForScalePos(double *dScaleIs,int iNotesInScaleIs,int ipos_in_scale);
static int PlayMidi_iNumOutDevs=0,PlayMidi_iNumOutDevsTotal=0;
static int PlayMidi_iQTdev=-1;
static char PlayMidi_midi_mapper_found=0;
static char PlayMidi_found_num_devs=0;

#ifdef compile_PM_as_DLL
int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
 return TRUE ;
}
#endif

PM_DLL_EXPORT int PM_DLL_CALLBACK nNumOutDevices(void)
{
	MIDIOUTCAPS __moc;
	memset(&__moc,0,sizeof(__moc));
 PlayMidi_iNumOutDevsTotal=0;

	if(!midiOutGetDevCaps (MIDIMAPPER, &__moc, sizeof (__moc)))
 {PlayMidi_iNumOutDevsTotal=1;PlayMidi_midi_mapper_found=1;}
 PlayMidi_iNumOutDevsTotal+=PlayMidi_iNumOutDevs = midiOutGetNumDevs ();
 if(CheckForQTLibrary())
 {
  PlayMidi_iQTdev=PlayMidi_iNumOutDevsTotal;
  PlayMidi_iNumOutDevsTotal++;
 }
 PlayMidi_found_num_devs=1;
 return PlayMidi_iNumOutDevsTotal;
}

PM_DLL_EXPORT char * PM_DLL_CALLBACK szNameForDevice(int iDev,char *szDeviceName,int iMAX_Name)
{
 if(!PlayMidi_found_num_devs)
  nNumOutDevices();
 szDeviceName[0]='\0';
 if(iDev==0&&PlayMidi_midi_mapper_found)
 {
  strcpy(szDeviceName,"Standard Device - Midi Mapper");
 }
 else
 if(iDev==PlayMidi_iQTdev)
 {
  strcpy(szDeviceName,"QuickTime");
 }
 else
 if(iDev<PlayMidi_iNumOutDevsTotal)
 {
	 MIDIOUTCAPS moc;
	 MMRESULT mmr;
	 char *szdevice=NULL;
	 memset(&moc,0,sizeof(moc));
	 mmr=midiOutGetDevCaps(iDev-(PlayMidi_midi_mapper_found?1:0),&moc, sizeof (moc));
	 szdevice=moc.szPname;
  switch(mmr)
  {
	 case MMSYSERR_NOERROR:break;
	 case MMSYSERR_NODRIVER:szdevice="(driver not installed)";break;
	 default:szdevice="(not available)";break;
	 }
  strncpy(szDeviceName,szdevice,iMAX_Name);
 }
 szDeviceName[iMAX_Name-1]='\0';
 return szDeviceName;
}


PM_DLL_EXPORT int PM_DLL_CALLBACK StartMidiOutForiDev(int iDev)
{
 if(!PlayMidi_found_num_devs)
  nNumOutDevices();
 if(iDev==PlayMidi_iQTdev)
  if(!StartQT())
   return MIDI_OUT_ERROR;
 PlayMidi_iMidiOutDevSel=iDev;
 return OpenMidiOut(iDev-(PlayMidi_midi_mapper_found?1:0));// midi mapper
}

PM_DLL_EXPORT int PM_DLL_CALLBACK StartMidiOut(void)
{
 return StartMidiOutForiDev(PlayMidi_iMidiOutDevSel);// midi mapper
}

PM_DLL_EXPORT void PM_DLL_CALLBACK StopMidiOut(void)
{
 if(PlayMidi_bOpened)
  CloseMidiOut();
}

PM_DLL_EXPORT int PM_DLL_CALLBACK OpenMidiOut(int iDevice)
{
 DWORD dwRet=0;
 // returns 1 for error, 2 if already open and 0 if successful
 if(PlayMidi_bOpened)
  return MIDI_OUT_ALREADY_OPEN;
 else
  if(PlayMidi_bUseQT||!(dwRet=midiOutOpen(&PlayMidi_hMidiOut, iDevice, 0, 0, 0)))
  {
   int i=0;
   PlayMidi_bOpened=TRUE ;
   if(!PlayMidi_bUseQT)
    PlayMidi_SetPitchBendRanges();
   PlayMidi_ResetMidiOutChannelStateRecords();
   SetVoicePatches(PlayMidi_iVoice);
   PlayMidi_dwtopen=timeGetTime();
   for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
    PlayMidi_dwtime_for_last_note_ends[i]=PlayMidi_dwtopen;
   return MIDI_OUT_OK;
  }
  else
  {
   return MIDI_OUT_ERROR;
  }
}

PM_DLL_EXPORT int PM_DLL_CALLBACK CloseMidiOut(void)
{
 if(PlayMidi_bOpened)
 {
  int i=0;
  midiOutClose(PlayMidi_hMidiOut);
  PlayMidi_hMidiOut=0;
  PlayMidi_bOpened=FALSE;
  PlayMidi_ResetMidiOutChannelStateRecords();
  return 1;
 }
 return 0;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK nChannelsAvailable (void)
{
 return PM_MAX_MIDI_CHANNELS_AVAILABLE;
}

PM_DLL_EXPORT double PM_DLL_CALLBACK dSecsSinceStartMidiOut(void)
{
 return (double) (timeGetTime()-PlayMidi_dwtopen)/1000.0;
}

PM_DLL_EXPORT void PM_DLL_CALLBACK  SleepSeconds(double dSecs)
{
 DWORD dwMs=(DWORD)(dSecs*1000);
 if(dSecs>0)
  Sleep(dwMs);
}

PM_DLL_EXPORT void PM_DLL_CALLBACK  Set1o1(double d1o1Freq)
{
 if(d1o1Freq==0)
  PlayMidi_d1o1Frequency=PlayMidi_FrequencyForMIDINote(dMidiOutFirstNote);
 else
  PlayMidi_d1o1Frequency=d1o1Freq;
}

PM_DLL_EXPORT void PM_DLL_CALLBACK  SetMidiScale(char *sz)
{
 if(PlayMidi_d1o1Frequency==0)
  PlayMidi_d1o1Frequency=PlayMidi_FrequencyForMIDINote(dMidiOutFirstNote);
 PlayMidi_iNotesInScale=2;
 PlayMidi_dScale[0]=1;
 PlayMidi_dScale[1]=exp((1/12.0)*log(2));// default to 12-et
 PlayMidi_first_scale_made=1;
 if(sz&&sz[0]!='\0')
 {
  int i=0;
  GetScaleFromText(PlayMidi_dScale,&PlayMidi_iNotesInScale,sz,PM_MAX_NOTES_IN_SCALE);
  // it's also good to set midi pitch bends for all the scale degrees
  // if possible.as some synths are slow to respond to pitch bends
  for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
   PlayMidi_dpitch_bend_for_channel[i]=-10;
  {
   int i=0;
   for(i=0;i<50;i++)
   {
    DoBendForScaleDegreeInLoop(i);
    if(i>0)
     DoBendForScaleDegreeInLoop(-i);
   }
  }
 }
}

PM_DLL_EXPORT void PM_DLL_CALLBACK  PM_DLL_CALLBACK SetMidiVoice(int iVoice)
{
 int i;
 for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
 if(i!=PM_NON_MELODIC_PERCUSSION_CHANNEL)
  PlayMidi_MidiOutShortMessage (PlayMidi_hMidiOut,PM_MIDI_SET_PATCH,i,iVoice,0,1);
 PlayMidi_iVoice=iVoice;
}

PM_DLL_EXPORT void PM_DLL_CALLBACK  TestPitchBendRange(void)
{
 int iChannelAP=0,iVelocity=127;
 PlayMidi_MidiPitchBend(PlayMidi_hMidiOut,iChannelAP,0);
 SleepSeconds(0.5);
 PlayMidi_MidiNoteOn(PlayMidi_hMidiOut,iChannelAP
            ,62,iVelocity
            );
 SleepSeconds(1);
 PlayMidi_MidiNoteOff(PlayMidi_hMidiOut,iChannelAP
           ,62,iVelocity
           );
 iChannelAP=1;
 PlayMidi_MidiPitchBend(PlayMidi_hMidiOut,iChannelAP,PM_MAX_MIDI_PITCH_BEND);
 SleepSeconds(0.5);
 PlayMidi_MidiNoteOn(PlayMidi_hMidiOut,iChannelAP
            ,58,iVelocity
            );
 SleepSeconds(1);
 PlayMidi_MidiNoteOff(PlayMidi_hMidiOut,iChannelAP
           ,58,iVelocity
           );
}

PM_DLL_EXPORT void PM_DLL_CALLBACK DoBendForScaleDegree(int ipos_in_scale)
{
 PlayMidi_DoBendForScaleDegreeExtraParam(0,ipos_in_scale);
}

PM_DLL_EXPORT void PM_DLL_CALLBACK DoBendForScaleDegreeInLoop(int ipos_in_scale)
{
 PlayMidi_DoBendForScaleDegreeExtraParam(NO_REBEND_FOR_MOST_RECENT_CHANN,ipos_in_scale);
}

static void PlayMidi_DoBendForScaleDegreeExtraParam(int flags,int ipos_in_scale)
{
 // call with flags==NO_REBEND_FOR_MOST_RECENT_CHANN
 // if you are doing many all in the same millisecond
 double dMultiplier=PlayMidi_dFindMultiplierForScalePos(PlayMidi_dScale,PlayMidi_iNotesInScale,ipos_in_scale);
 double dFreq=PlayMidi_d1o1Frequency*dMultiplier;
 int iNoteToPlay=0;
 double dPitchBend=PM_IPITCH_BEND_ZERO_POS;
 int iChannelAP=0;
 PlayMidi_FindChannelNoteAndPitchBend(flags,&iChannelAP,&iNoteToPlay,&dPitchBend,dFreq);
 PlayMidi_MidiPitchBend(PlayMidi_hMidiOut,iChannelAP,dPitchBend);
}

PM_DLL_EXPORT void PM_DLL_CALLBACK PlayNonMelodicPercussion(int iNote,int iVelocity)
{
 PlayMidi_MidiNoteOn
  (PlayMidi_hMidiOut,PM_NON_MELODIC_PERCUSSION_CHANNEL
  ,iNote,iVelocity
  );
 PlayMidi_AddNoteInPlayRecord(iNote,1/*dMultiplier*/,PM_NON_MELODIC_PERCUSSION_CHANNEL,iNote);
}

PM_DLL_EXPORT void PM_DLL_CALLBACK StopNonMelodicPercussion(int iNote,int iVelocity)
{
 int i=0;
 for(i=0;i<PlayMidi_inotes_in_play;i++)
 if(PlayMidi_iScaleDegreeInPlay[i]==iNote)
 if(PlayMidi_iChannelInPlay[i]==PM_NON_MELODIC_PERCUSSION_CHANNEL)
 {
  PlayMidi_MidiNoteOff
   (PlayMidi_hMidiOut,PM_NON_MELODIC_PERCUSSION_CHANNEL
   ,iNote,127
   );
  PlayMidi_RemoveNoteInPlayRecord(i);
 }
}

PM_DLL_EXPORT void PM_DLL_CALLBACK  PlayScaleDegree(int ipos_in_scale,int iVelocity)
{
 if(!PlayMidi_first_scale_made)
 {
  SetMidiScale("");
 }
 {
  double dMultiplier=PlayMidi_dFindMultiplierForScalePos(PlayMidi_dScale,PlayMidi_iNotesInScale,ipos_in_scale);
  double dFreq=PlayMidi_d1o1Frequency*dMultiplier;
  int iNoteToPlay=0;
  double dPitchBend=PM_IPITCH_BEND_ZERO_POS;
  int iChannelAP=0;
  if(PlayMidi_inotes_in_play>=PM_MAX_NOTES_IN_PLAY)
   return;
  PlayMidi_FindChannelNoteAndPitchBend(OK_TO_SWITCH_OFF,&iChannelAP,&iNoteToPlay,&dPitchBend,dFreq);
  PlayMidi_MidiPitchBend(PlayMidi_hMidiOut,iChannelAP,dPitchBend);
  PlayMidi_MidiNoteOn
   (PlayMidi_hMidiOut,iChannelAP
   ,iNoteToPlay,iVelocity
   );
  PlayMidi_AddNoteInPlayRecord(ipos_in_scale,dMultiplier,iChannelAP,iNoteToPlay);
 }
}

PM_DLL_EXPORT void PM_DLL_CALLBACK StopScaleDegree(int ipos_in_scale,int iVelocity)
{
 int i=0,j=0;
 for(i=0;i<PlayMidi_inotes_in_play;i++)
 if(PlayMidi_iScaleDegreeInPlay[i]==ipos_in_scale)
 if(PlayMidi_iChannelInPlay[i]!=PM_NON_MELODIC_PERCUSSION_CHANNEL)
 {
  PlayMidi_MidiNoteOff(PlayMidi_hMidiOut,PlayMidi_iChannelInPlay[i]
             ,PlayMidi_iNoteInPlay[i],iVelocity
             );
  PlayMidi_RemoveNoteInPlayRecord(i);
  PlayMidi_dwtime_for_last_note_ends[i]=timeGetTime();
  return;
 }
}

PM_DLL_EXPORT void PM_DLL_CALLBACK PrintNotesInPlay(void)
{
 int i=0;
 int ichann=0;
 char first_note_in_ch=1;
 // diagnostic routine to show notes in play for console app
 // - replace with suitable Windows routine for Windows apps.
 printf("These are the notes in play for each channel:\n\n"
       "Each note shows:\n"
       "Interval from 1/1 in cents (Scale degree) Midi note]\n",ichann
       );
 for(ichann=0;ichann<PM_MAX_MIDI_CHANNELS_AVAILABLE;ichann++)
 {
  first_note_in_ch=1;
  for(i=0;i<PlayMidi_inotes_in_play;i++)
  if(PlayMidi_iChannelInPlay[i]==ichann)
  {
   if(first_note_in_ch)
    printf("\nChann:, %d: pitch bend %g cents, playing: "
          ,ichann,PM_SEMITONES_FOR_PITCH_BEND(PlayMidi_dpitch_bend_for_channel[ichann])*100
          );
   else
    printf(", ");
   printf("%g (%d) [%d]"
         ,pm_find_cents_for(PlayMidi_d1o1MultiplierInPlay[i])
         ,PlayMidi_iScaleDegreeInPlay[i]
         ,PlayMidi_iNoteInPlay[i]
         );
   first_note_in_ch=0;
  }
 }
 printf("\n");
}

static void PlayMidi_AddNoteInPlayRecord(int ipos_in_scale,double dMultiplier,int iChannelAP,int iNoteToPlay)
{
 if(PlayMidi_inotes_in_play<PM_MAX_NOTES_IN_PLAY)
 {
  PlayMidi_iScaleDegreeInPlay[PlayMidi_inotes_in_play]=ipos_in_scale;
  PlayMidi_d1o1MultiplierInPlay[PlayMidi_inotes_in_play]=dMultiplier;
  PlayMidi_iChannelInPlay[PlayMidi_inotes_in_play]=iChannelAP;
  PlayMidi_iNoteInPlay[PlayMidi_inotes_in_play]=iNoteToPlay;
  PlayMidi_inotes_in_play++;
 }
}

static void PlayMidi_RemoveNoteInPlayRecord(int ipos_in_list)
{
 int i=0;
 for(i=ipos_in_list;i<PlayMidi_inotes_in_play;i++)
 {
   PlayMidi_iScaleDegreeInPlay[i]=PlayMidi_iScaleDegreeInPlay[i+1];
   PlayMidi_iChannelInPlay[i]=PlayMidi_iChannelInPlay[i+1];
   PlayMidi_iNoteInPlay[i]=PlayMidi_iNoteInPlay[i+1];
   PlayMidi_d1o1MultiplierInPlay[i]=PlayMidi_d1o1MultiplierInPlay[i+1];
 }
 PlayMidi_inotes_in_play--;
}

static BOOL PlayMidi_bFindNoteRecord(int iScaleDegree,int iChannel,int iNote,int *pi)
{
 int i=0;
 // use non negative argument for values to look for
 for(i=0;i<PlayMidi_inotes_in_play;i++)
 {
  if(iScaleDegree<0||iScaleDegree==PlayMidi_iScaleDegreeInPlay[i])
  if(iChannel<0||iChannel==PlayMidi_iChannelInPlay[i])
  if(iNote<0||iNote==PlayMidi_iNoteInPlay[i])
  {
   if(pi)
    *pi=i;
   return TRUE;
  }
 }
 return FALSE;
}

PM_DLL_EXPORT void StopAllNotesPlayed(int iVelocity)
{
 int i=0,j=0;
 for(i=0;i<PlayMidi_inotes_in_play;i++)
 {
  PlayMidi_MidiNoteOff(PlayMidi_hMidiOut,PlayMidi_iChannelInPlay[i]
             ,PlayMidi_iNoteInPlay[i],iVelocity
             );
 }
 PlayMidi_inotes_in_play=0;
}

PM_DLL_EXPORT void StopAllNotesPlayedInChannel(int iVelocity,int iChannel)
{
 int i=0,j=0;
 for(i=0;i<PlayMidi_inotes_in_play;i++)
 {
  if(iChannel<0||PlayMidi_iChannelInPlay[i]==iChannel)
  {
   PlayMidi_MidiNoteOff(PlayMidi_hMidiOut,PlayMidi_iChannelInPlay[i]
              ,PlayMidi_iNoteInPlay[i],iVelocity
              );
   PlayMidi_RemoveNoteInPlayRecord(i);
  }
 }
}

#define OK_TO_SWITCH_OFF 1
#define NO_REBEND_FOR_MOST_RECENT_CHANN 2
static void PlayMidi_FindChannelNoteAndPitchBend
 (int flags
 ,int *piChannelAP,int *piNoteToPlay,double *pdPitchBend,double dFreq
 )
{
 // call with ok_to_switch_off == TRUE unless you are using this to allocate the
 // pitch bends in advance of playing and will be calling this again before
 // the note gets played.- otherwise no guarantee that a suitable channel
 // has been found
 // If initialising them all at the same moment of time
 // then use no_rebend_for_most_recent_chann
 BOOL ok_to_switch_off=flags&OK_TO_SWITCH_OFF;
 BOOL no_rebend_for_most_recent_chann=flags&NO_REBEND_FOR_MOST_RECENT_CHANN;
 double dMidiNote=PlayMidi_MIDINoteForFrequency(dFreq);
 int iNote=max(0,min(PM_TOP_PM_MIDI_NOTE,PM_NEAREST_INT(dMidiNote)));
 double dPitchBend=PM_PITCH_BEND_FOR_SEMITONES(dMidiNote-iNote);
 int iPitchBend=PM_NEAREST_INT(dPitchBend);
 int i;
 int iChannel=-1;
 dPitchBend=max(0,min(PM_MAX_MIDI_PITCH_BEND,dPitchBend));
 iPitchBend=max(0,min(PM_MAX_MIDI_PITCH_BEND,iPitchBend));
 //find channel that already has this pitch bend
 for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
  if(i!=PM_NON_MELODIC_PERCUSSION_CHANNEL)
  if(PM_NEAREST_INT(PlayMidi_dpitch_bend_for_channel[i])==iPitchBend)
  {iChannel=i;break;}
 //if none found, find channel with no pitch bend set yet
 if(iChannel==-1)
 for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
  if(i!=PM_NON_MELODIC_PERCUSSION_CHANNEL)
  if(!PlayMidi_bFindNoteRecord(-1/*iScaleDegree*/,i/*iChannel*/,iNote,NULL))
   // This prevents one from playing the same note number multiple times in the
   // same channel. Some high end sound cards handle this properly
   // but many can't - e.g. switch both off when you switch off one of them.
  if(PlayMidi_dpitch_bend_for_channel[i]==-10)
  {
   iChannel=i;break;
  }
 // if none found, look for one to switch off all notes in
 // - look for least recently used one if there are several
 if(iChannel==-1)
 {
  DWORD dwtmin=0;
  char first_one=1;
  DWORD dwtmostrecent=0;
  if(no_rebend_for_most_recent_chann)
  {
   for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
    dwtmostrecent=max(dwtmostrecent,PlayMidi_dwtime_for_last_note_ends[i]);
  }
  for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
   if(i!=PM_NON_MELODIC_PERCUSSION_CHANNEL)
   if(PlayMidi_nnotes_in_play_for_channel[i]==0)
   {
    DWORD dwt=PlayMidi_dwtime_for_last_note_ends[i];
    if(dwt<dwtmostrecent||no_rebend_for_most_recent_chann==0)
    if(first_one||dwt<dwtmin)
    {iChannel=i;dwtmin=dwt;}
    first_one=0;
   }
  //last remaining case - 15 notes all sounding sim. with all pitch bends different
  //Could happen e.g. in large harmonic series chord.
  if(iChannel==-1)
  {
   if(ok_to_switch_off)
   {
    first_one=1;
    for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
    if(i!=PM_NON_MELODIC_PERCUSSION_CHANNEL)
    {
     DWORD dwt=PlayMidi_dwtime_for_last_note_ends[i];
     if(first_one||dwt<dwtmin)
     {iChannel=i;dwtmin=dwt;}
     first_one=0;
    }
    StopAllNotesPlayedInChannel(127/*Velocity*/,iChannel);
   }
  }
 }
 *piChannelAP=iChannel;
 *piNoteToPlay=iNote;
 *pdPitchBend=dPitchBend;
}


static DWORD PlayMidi_MidiPitchBend(HMIDIOUT hMidi,int iChannel,double dPitchBend)
{
 // note that the dPitchBend is a double - this is so that it can
 // be recorded to maximum precision internally - which is useful
 // if later on you want to know the exact intended cents value
 // or ratio for a note in play.
 DWORD dwReturn;
 int iPrevPitchBend=PM_NEAREST_INT(PlayMidi_dpitch_bend_for_channel[iChannel]);
 int iPitchBend=PM_NEAREST_INT(dPitchBend);
 iPrevPitchBend=max(0,min(PM_MAX_MIDI_PITCH_BEND,iPrevPitchBend));
 iPitchBend=max(0,min(PM_MAX_MIDI_PITCH_BEND,iPitchBend));
 if(iChannel<0||iChannel>=PM_MAX_MIDI_CHANNELS_AVAILABLE)
  return 0;
 if(iPrevPitchBend!=iPitchBend)
 {
  dwReturn=PlayMidi_MidiOutShortMessage (hMidi,0xE0,iChannel,iPitchBend&0x7F,iPitchBend>>7,2);
 }
 PlayMidi_dpitch_bend_for_channel[iChannel]=dPitchBend;
 return dwReturn;
}

static DWORD PlayMidi_MidiOutShortMessage(HMIDIOUT hMidi,int iStatus,int iChannel
                    ,int iData1,int iData2,int size
                    )
{
 DWORD dwMessage=0;
 DWORD dwReturn=0;
 DWORD dwret=0;
 if(PlayMidi_bUseQT&&pQTMusic_SendMidiEvent)
 {
  pQTMusic_SendMidiEvent(iStatus,iChannel,iData1,iData2);
  dwret=0;
 }
 else
 if(hMidi)
 {
  iData1&=0xff;
  iData2&=0xff;
  iStatus&=0xf0;
  iChannel&=0x0f;
  dwMessage = iStatus | iChannel | (iData1 << 8) | (iData2 << 16) ;
  return midiOutShortMsg (hMidi,dwMessage);
 }
 return dwret;
}


static DWORD PlayMidi_MidiNoteOff(HMIDIOUT hMidi,int iChannel,int iNote,int iVel)
{
 iVel=max(0,min(PM_MAX_MIDI_VELOCITY,iVel));
 iChannel=max(0,min(PM_MAX_MIDI_CHANNELS_AVAILABLE-1,iChannel));
 iNote=max(0,min(PM_MAX_MIDI_NOTES-1,iNote));
 PlayMidi_nnotes_in_play_for_channel[iChannel]--;
 return PlayMidi_MidiOutShortMessage(hMidi,PM_MIDI_NOTE_OFF,iChannel,iNote,iVel,2);
}

static DWORD PlayMidi_MidiNoteOn(HMIDIOUT hMidi,int iChannel,int iNote,int iVel)
{
 iVel=max(0,min(PM_MAX_MIDI_VELOCITY,iVel));
 iChannel=max(0,min(PM_MAX_MIDI_CHANNELS_AVAILABLE-1,iChannel));
 iNote=max(0,min(PM_MAX_MIDI_NOTES-1,iNote));
 PlayMidi_nnotes_in_play_for_channel[iChannel]++;
 return PlayMidi_MidiOutShortMessage(hMidi,PM_MIDI_NOTE_ON,iChannel,iNote,iVel,2);
}

static void PlayMidi_MidiSetPitchBendRange(HMIDIOUT hMidi,int iChannel,int coarse_range,int fine_range)
{
 //coarse_range is in semitones, fine_range in cents
 //specify that it's the pitch bend sensitivity controller = registered parameter number 0
 PlayMidi_MidiOutShortMessage (hMidi,PM_MIDI_CONTROLLER,iChannel,PM_RPN_COARSE_COARSE,0/*pitch bend sensitivity*/,2) ;
 PlayMidi_MidiOutShortMessage (hMidi,PM_MIDI_CONTROLLER,iChannel,PM_RPN_COARSE_FINE,0/*pitch bend sensitivity*/,2) ;
 //set range for the rpn
 PlayMidi_MidiOutShortMessage (hMidi,PM_MIDI_CONTROLLER,iChannel,PM_DATA_ENTRY_PB_COARSE,coarse_range,2) ;
 PlayMidi_MidiOutShortMessage (hMidi,PM_MIDI_CONTROLLER,iChannel,PM_DATA_ENTRY_PB_FINE,fine_range,2) ;
}

static void PlayMidi_SetPitchBendRanges(void)
{
 int i=0;
 for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
  if(i!=PM_NON_MELODIC_PERCUSSION_CHANNEL)
  //set to 2 semitiones.
  //this is default, so prob. not nec., but may as well set it to be sure.
  //N.B. can be slow - should only do once, e.g. on opening Midi Out.
  PlayMidi_MidiSetPitchBendRange(PlayMidi_hMidiOut,i,2,0);
           //2 semitones = GMIDI spec. default value, so prob. not nec.
}

static void PlayMidi_ResetMidiOutChannelStateRecords(void)
{
 int i;
 for(i=0;i<PM_MAX_MIDI_CHANNELS_AVAILABLE;i++)
 {
  PlayMidi_dpitch_bend_for_channel[i]=-10;
  PlayMidi_nnotes_in_play_for_channel[i]=0;
  if(i!=PM_NON_MELODIC_PERCUSSION_CHANNEL)
   PlayMidi_MidiPitchBend(PlayMidi_hMidiOut,i,PM_IPITCH_BEND_ZERO_POS);
 }
}

static int PlayMidi_substrcmpi(const char * sz,const char * szsubstr)
//returns 0 if the same, or szsubstr a sub string of sz
{
 long i=0;
 char c1[2],c2[2];
 if(sz==NULL||szsubstr==NULL)
  return 0;
 for(i=0;;i++)
 {
  c1[0]=sz[i];
  c2[0]=szsubstr[i];
  if(c2[0]=='\0')
   return 0;
  CharUpper(c1); // actually could use toupper() for the instrument names - CharUpper is the
                 // routine recommended for Windows for accented characters.
  CharUpper(c2);
  if(c1[0]!=c2[0])
   return i+1;
 }
}

static int PlayMidi_remove_duplicated_white_space(char *sz)
{
 long i=0,ipos=0;
 char was_space=1;// so leading white space is ignored
 if(sz==NULL)
  return FALSE;
 for(i=0;;i++)
 {
  char c=sz[i];
  if(c=='\0')
  {sz[ipos++]=c;return ipos;}
  if(isspace(c))
  {
   if(!was_space)
    sz[ipos++]=' ';
   was_space=1;
   continue;
  }
  was_space=0;
  sz[ipos++]=c;
 }
}

PM_DLL_EXPORT char * PM_DLL_CALLBACK GetMidiVoiceName(int iVoice,char *szVoiceFound,int iMax_szVoiceFound)
{
 szVoiceFound[0]='\0';
 strncpy(szVoiceFound,PlayMidi_szMidiVoices[iVoice],iMax_szVoiceFound);
 szVoiceFound[iMax_szVoiceFound-1]='\0';
 return szVoiceFound;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK FindMidiVoice(char *szWords,char *szVoiceFound,int iMax_szVoiceFound)
{
 // szWords can either be any word in the voice name, or the voice number as a non negative integer
 int iVoice=0;
 char szWordsCopy[101];
 strncpy(szWordsCopy,szWords,100);
 szWords[100]='\0';
 PlayMidi_remove_duplicated_white_space(szWordsCopy);
 switch(szWordsCopy[0])
 {
 case '+':
 case '0':
 case '1':
 case '2':
 case '3':
 case '4':
 case '5':
 case '6':
 case '7':
 case '8':
 case '9':
  iVoice=atoi(szWordsCopy);
  break;
 default:;
  {
   int j=0;
   // preferrentially look for whole phrase, then whole words, then starts of words
   for(iVoice=0;iVoice<PM_MAX_MIDI_VOICES;iVoice++)
   if(strcmpi(szWordsCopy,PlayMidi_szMidiVoices[iVoice])==0)
    goto to_return;
   for(iVoice=0;iVoice<PM_MAX_MIDI_VOICES;iVoice++)
   for(j=0;PlayMidi_szMidiVoices[iVoice][j];j++)
    if(PlayMidi_substrcmpi(&PlayMidi_szMidiVoices[iVoice][j],szWordsCopy)==0)
    {
     char c=PlayMidi_szMidiVoices[iVoice][j+strlen(szWordsCopy)];
     if(j==0||isspace(PlayMidi_szMidiVoices[iVoice][j-1]))
     {
      if(c=='\0'||isspace(c))
       goto to_return;
     }
    }
   for(iVoice=0;iVoice<PM_MAX_MIDI_VOICES;iVoice++)
   for(j=0;PlayMidi_szMidiVoices[iVoice][j];j++)
    if(PlayMidi_substrcmpi(&PlayMidi_szMidiVoices[iVoice][j],szWordsCopy)==0)
    if(j==0||isspace(PlayMidi_szMidiVoices[iVoice][j-1]))
     goto to_return;
   for(iVoice=0;iVoice<PM_MAX_MIDI_VOICES;iVoice++)
   for(j=0;PlayMidi_szMidiVoices[iVoice][j];j++)
    if(PlayMidi_substrcmpi(&PlayMidi_szMidiVoices[iVoice][j],szWordsCopy)==0)
     goto to_return;
  }
  iVoice=0;
  break;
 }
 to_return:;
 iVoice=max(0,min(PM_MAX_MIDI_VOICES,iVoice));
 strncpy(szVoiceFound,PlayMidi_szMidiVoices[iVoice],iMax_szVoiceFound-1);
 return iVoice;
}

PM_DLL_EXPORT void PM_DLL_CALLBACK PrintVoices(void)
{
 int iVoice=0;
 int ipos_line=0;
 for(iVoice=0;iVoice<PM_MAX_MIDI_VOICES;iVoice++)
 {
  ipos_line+=printf("%s (%d), ",PlayMidi_szMidiVoices[iVoice],iVoice);
  if(ipos_line>=50)
  {
   printf("\n");
   ipos_line=0;
  }
  if(iVoice==64)
  {
   printf("\n(continues...)");
   getchar();
  }
 }
 printf("\n");
 getchar();
}


// scales

static int next_token_pos_for_white_space(char *sz,int *pireturn)
/*
goes through the string like strtok,call with sz = string to search
each time, and call with *pireturn<0 first time.
Sets *pireturn to start of next token as coord rather than pointer.
Doesn't add any '\0's to separate the tokens.
Sets ipos to -1 for end of string.
Returns 1, or 0 for end of string.
extra_c == extra separator
ignore_c== white space to treat as non separator - use ignore_c==-1 to ignore all white space.
*/
{
int ipos=*pireturn;
if(sz==NULL)
{*pireturn=-1;return 0;}
/**dbg_ntp++;**/
if(ipos<0)
 ipos=0;
else
 {
 /*find next separator*/
 for(;;ipos++)
  {
  char c=sz[ipos];
  switch(c)
   {
   case '\0':
    goto found_separator;
#define CASE_ISSPACE  \
       case '\r':     \
       case '\n':     \
       case ' ' :     \
       case '\v':     \
       case '\t':

   CASE_ISSPACE
     goto found_separator;
   break;
   }
  }
 }
/*ready for next token search*/
found_separator:;
/*find next non separator*/
for(;;ipos++)
 {
  char c=sz[ipos];
  if(c=='\0')
   {
   *pireturn=-1;
   return 0;
   }
  switch(c)
   {
   CASE_ISSPACE
    break;
   default:
    *pireturn=ipos;
    return 1;
  }
 }
}

static int copy_token(char *lptoken,char *lp,int ipos,int imax_copy)
/*
returns number of characters copied
ipos=pos to copy them from in lp
*/
{
int i=0;
if(lp==NULL||lptoken==NULL||ipos<0)
 return 0;
 /*find next separator*/
 for(;i<imax_copy;ipos++)
  {
  char c=lp[ipos];
  if(c=='\0')
   break;
  if(isspace(c))
     break;
  lptoken[i++]=c;
  }
lptoken[max(0,min(imax_copy-1,i))]='\0';
return i;
}

#define MAX_SZ_MOST_RECENT_SCALE 1024
char szMostRecentScale[MAX_SZ_MOST_RECENT_SCALE];

PM_DLL_EXPORT char * PM_DLL_CALLBACK GetszMostRecentScale(void)
{
 return szMostRecentScale;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK GetnScaleNotes(void)
{
 return PlayMidi_iNotesInScale;
}

PM_DLL_EXPORT void PM_DLL_CALLBACK GetScaleFromText(double *dScaleIs,int *piNotesInScale,char *szScale,int iPM_MAX_SCALE)
{
 //n.b. uses strtok, so modifies szBuffer
 double dvalue=0;
 int ipos=-1;
 int iscale=0;
 char is_first_note=1;
#define STRMAX 128
 char sztoken[STRMAX];
 strncpy(szMostRecentScale,szScale,MAX_SZ_MOST_RECENT_SCALE);
 szMostRecentScale[MAX_SZ_MOST_RECENT_SCALE-1]='\0';
 sztoken[0]='\0';
 dScaleIs[0]=1;
 iscale++;
 for(;;)
 {
  char is_n_et_notation=0;
  if(!next_token_pos_for_white_space(szScale,&ipos))
   break;
  copy_token(sztoken,szScale,ipos,STRMAX);
  if(is_first_note&&sztoken[0]=='h')
  {
   iscale=0;//harmonic series scale
   continue;
  }
  if(sztoken[0]=='n'&&sztoken[1]=='(')
  {
   is_n_et_notation=1;
   copy_token(sztoken,szScale,ipos+2,STRMAX);
  }
  if(strcmpi(sztoken,"cents")==0)
  {
   //previous value needs to be reread as cents
   //i.e. as 2^(dvalue/1200)
   ipos=max(0,ipos-1);
   dScaleIs[ipos++]=pm_find_value_for_cents(dvalue);
  }
  else
  {
   //see if there is a '/' sign in the token. If so, it's a ratio
   int i;
   char is_ratio=0;
   char is_cents=0;
   for(i=0;sztoken[i];i++)
   if(sztoken[i]=='/')
   {
    sztoken[i]='\0';
    dvalue=atof(sztoken)/atof(&sztoken[i+1]);
    sztoken[i]='/';
    is_ratio=1;
    break;
   }
   if(!is_ratio)
   {
    for(i=0;sztoken[i];i++)
    if(sztoken[i]=='.')
    {
     is_cents=1;
     break;
    }
    dvalue=atof(sztoken);
   }
   if(is_cents==0&&fabs(dvalue)<1e-10)
    continue;
   if(is_cents)
    dScaleIs[iscale++]=exp((dvalue/1200)*log(2));
   else
   if(is_n_et_notation)
    dScaleIs[iscale++]=exp((dvalue)*log(2));
   else
    dScaleIs[iscale++]=dvalue;
  }
  if(iscale>=PM_MAX_NOTES_IN_SCALE-1)
   break;
 }
 *piNotesInScale=iscale;
}

static double PlayMidi_dFindMultiplierForScalePos(double *dScaleIs,int iNotesInScaleIs,int ipos_in_scale)
{
 double dMultiplier=1;
 if(iNotesInScaleIs>0)
 {
  if(fabs(dScaleIs[0]-1)<=1e-5)
  {
    //normal case - scale starts with 1/1
   int iSpans=0,iPos=0;
   double dSpan=dScaleIs[iNotesInScaleIs-1];
   if(iNotesInScaleIs>1)
   {
    //multipliers are dScaleIs[0], dScaleIs[1],.., dScaleIs[iNotesInScaleIs]
    //then for next span
    //dScaleIs[1]*dSpan,..,dScaleIs[iNotesInScaleIs]*dSpan
    //where dSpan=dScaleIs[iNotesInScaleIs] - might not nec. be an octave
    if(ipos_in_scale>=0)
     iSpans=ipos_in_scale/(iNotesInScaleIs-1);
    else
     // iSpans == -1 for first octave below start of scale
     iSpans=ipos_in_scale/(iNotesInScaleIs-1)-1;
    if(ipos_in_scale>=0)
     iPos=ipos_in_scale%(iNotesInScaleIs-1);
    else
    {
     int iPosInOctave=(-ipos_in_scale)%(iNotesInScaleIs-1);//starts at 1 for ipos_in_scale==-1
     iPos=iNotesInScaleIs-1-iPosInOctave;//starts at ipos_in_scale-1
    }
   }
   else
    iPos=ipos_in_scale;
   if(iNotesInScaleIs>1)
   {
    //normal case
    double dSpansMultiplier;
    if(iSpans==0)
     dSpansMultiplier=1;
    else
     dSpansMultiplier=pm_safe_exp((double)iSpans*pm_safe_log(dSpan));
    dMultiplier=dSpansMultiplier*(dScaleIs[iPos]);
   }
  }
  else
   // harmonic series notation 2 for harmonic / subharmonic series
   // and 6/5 for harmonic series starting from 5th overtone as the 1/1, etc.
  {
   if(ipos_in_scale==0)
    dMultiplier=1;
   else
   {
    dMultiplier=1+(dScaleIs[0]-1)*abs(ipos_in_scale);
    if(ipos_in_scale<0)
     dMultiplier=1/dMultiplier;
   }
  }
  return dMultiplier;
 }
 else
  return 1;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK CheckForQTLibrary(void)
{
 if(!hQTJavaNative)
  hQTJavaNative = LoadLibrary(TEXT("QTJavaNative.dll"));
 if(hQTJavaNative)
 {
  if(!hQTMusic)
   hQTMusic = LoadLibrary(TEXT("QTMusicLib.dll"));
  if(hQTMusic)
  {
   pQTMusic_CleanUp_SME=(PROC)GetProcAddress(hQTMusic, TEXT("_QTMusic_CleanUp_SME@0"));
   pQTMusic_Initialise_SME=(PROC)GetProcAddress(hQTMusic, TEXT("_QTMusic_Initialise_SME@0"));
   pQTMusic_Delay=(PROC)GetProcAddress(hQTMusic, TEXT("_QTMusic_Delay@8"));
   pQTMusic_SendMidiEvent=(PROC)GetProcAddress(hQTMusic, TEXT("_QTMusic_SendMidiEvent@16"));
   return TRUE;
  }
 }
 return FALSE;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK StartQT(void)
{
 if(CheckForQTLibrary())
 {
  PlayMidi_bUseQT=TRUE;
  return pQTMusic_Initialise_SME();
 }
 else
  StopQT();
 return FALSE;
}


PM_DLL_EXPORT void PM_DLL_CALLBACK StopQT(void)
{
 if(PlayMidi_bUseQT)
 {
  int i=0;
  if(pQTMusic_CleanUp_SME)
   pQTMusic_CleanUp_SME();
  if(hQTJavaNative)
   FreeLibrary(hQTJavaNative);
  if(hQTMusic)
   FreeLibrary(hQTMusic);
  hQTMusic=hQTJavaNative=NULL;
  PlayMidi_bUseQT=FALSE;
 }
}

#ifdef PM_ADD_TO_WINDOWS_PROJECT

PM_DLL_EXPORT int PM_DLL_CALLBACK GetListSelMidiOutDevice(HWND hwndListBox,WPARAM wParam)
{
 if(HIWORD(wParam)==LBN_SELCHANGE)
  PlayMidi_iMidiOutDevSel=SendMessage(hwndListBox,LB_GETCURSEL,0,0L);
 return PlayMidi_iMidiOutDevSel;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK GetComboSelMidiOutDevice(HWND hwndComboBox,WPARAM wParam)
{
 if(HIWORD(wParam)==CBN_SELENDOK)
  PlayMidi_iMidiOutDevSel=SendMessage(hwndComboBox,CB_GETCURSEL,0,0L);
 return PlayMidi_iMidiOutDevSel;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK SetListSelMidiOutDevice(HWND hwndListBox,int iDev)
{
 return SendMessage(hwndListBox,LB_SETCURSEL,iDev,0L);
}

PM_DLL_EXPORT int PM_DLL_CALLBACK SetComboSelMidiOutDevice(HWND hwndComboBox,int iDev)
{
 return SendMessage(hwndComboBox,CB_SETCURSEL,iDev,0L);
}

PM_DLL_EXPORT int PM_DLL_CALLBACK FillComboWithMidiOutDevices(HWND hwndCombo)
{
 return PlayMidi_FillListOrComboWithMidiOutDevices(hwndCombo,TRUE/*is_combo*/);
}

PM_DLL_EXPORT int PM_DLL_CALLBACK FillListWithMidiOutDevices(HWND hwndList)
{
 return PlayMidi_FillListOrComboWithMidiOutDevices(hwndList,FALSE/*is_combo*/);
}

static int PlayMidi_FillListOrComboWithMidiOutDevices(HWND hwnd,BOOL is_combo)
{
	int i;
#define MAX_szDeviceName 1024
 char szDeviceName[MAX_szDeviceName];
 int nNumOutDevs=nNumOutDevices();
 if(!hwnd)
  return 0;
	SendMessage(hwnd,WM_SETREDRAW,FALSE,0);
	SendMessage(hwnd,is_combo?CB_RESETCONTENT:LB_RESETCONTENT,0,0);
	for(i=0;i<nNumOutDevs;i++)
 {
		SendMessage
   (hwnd
   ,is_combo?CB_ADDSTRING:LB_ADDSTRING,0
   ,(LPARAM)szNameForDevice(i,szDeviceName,MAX_szDeviceName)
   );
 }
 SendMessage(hwnd,is_combo?CB_SETCURSEL:LB_SETCURSEL,PlayMidi_iMidiOutDevSel,0L);
	SendMessage(hwnd,WM_SETREDRAW,TRUE,0);
 return PlayMidi_iMidiOutDevSel;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK SetListSelMidiVoice(HWND hwndList,int iDev)
{
 return PlayMidi_iVoice=SendMessage(hwndList,LB_GETCURSEL,iDev,0L);
}

PM_DLL_EXPORT int PM_DLL_CALLBACK SetComboSelMidiVoice(HWND hwndCombo,int iDev)
{
 return PlayMidi_iVoice=SendMessage(hwndCombo,CB_GETCURSEL,iDev,0L);
}

PM_DLL_EXPORT int PM_DLL_CALLBACK GetListSelMidiVoice(HWND hwndListBox,WPARAM wParam)
{
 if(HIWORD(wParam)==LBN_SELCHANGE)
  PlayMidi_iVoice=SendMessage(hwndListBox,LB_GETCURSEL,0,0L);
 return PlayMidi_iVoice;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK GetComboSelMidiVoice(HWND hwndComboBox,WPARAM wParam)
{
 if(HIWORD(wParam)==CBN_SELENDOK)
  PlayMidi_iVoice=SendMessage(hwndComboBox,CB_GETCURSEL,0,0L);
 return PlayMidi_iVoice;
}

PM_DLL_EXPORT int PM_DLL_CALLBACK FillComboWithMidiVoices(HWND hwndCombo)
{
 return PlayMidi_FillListOrComboWithMidiVoices(hwndCombo,TRUE/*is_combo*/);
}

PM_DLL_EXPORT int PM_DLL_CALLBACK FillListWithMidiVoices(HWND hwndList)
{
 return PlayMidi_FillListOrComboWithMidiVoices(hwndList,FALSE/*is_combo*/);
}

static int PlayMidi_FillListOrComboWithMidiVoices(HWND hwnd,BOOL is_combo)
{
	int i;
#define MAX_szDeviceName 1024
 if(!hwnd)
  return 0;
	SendMessage(hwnd,WM_SETREDRAW,FALSE,0);
	SendMessage(hwnd,is_combo?CB_RESETCONTENT:LB_RESETCONTENT,0,0);
	for(i=0;i<MAX_MIDI_VOICES;i++)
 {
		SendMessage
   (hwnd
   ,is_combo?CB_ADDSTRING:LB_ADDSTRING,0
   ,(LPARAM)PlayMidi_szMidiVoices[i]
   );
 }
 SendMessage(hwnd,is_combo?CB_SETCURSEL:LB_SETCURSEL,PlayMidi_iVoice,0L);
	SendMessage(hwnd,WM_SETREDRAW,TRUE,0);
 return PlayMidi_iVoice;
}

#endif
